#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <unordered_map>
#include <cstdio>
#include <cstdarg>

/// fix模式的线程池，是不需要保证队列里面的线程安全问题。
///
/// 线程支持的模式
enum ThreadPoolMode {
    ThreadPoolModeFIXED, // 固定数量的线程   -- 不需要考虑线程安全问题
    ThreadPoolModeCACHED, // 线程数量可动态增长 -- 需要考虑线程安全问题
};

///线程类
class Thread {
public:
    using ThreadFunc = std::function<void(int)>;
    Thread(ThreadFunc func) : _func(func), _threadId(_generateId++) {}
    ~Thread() = default;
    /// 启动线程
    void start() ;
    /// 获取线程ID
    int getId() const;

private:
    ThreadFunc _func;
    /// 线程ID生成器
    static int _generateId;
    int _threadId;
};

// 定义一个函数指针类型，指向具有 printf 签名的函数
typedef void (*LogLog)(int level,const char* format, ...);

class Thread;
/// 线程池
/// <summary>
/// 已知问题，在存取任务时没有使用好锁导致任务使用有问题
/// 或者说线程初始化太同步了，导致两个线程取到了同一个任务  fixed
/// </summary>
class ThreadPool {

private:
    static ThreadPool *singleton;
    ThreadPool();
    // 不希望线程池对象进行拷贝构造和赋值
    ThreadPool(const ThreadPool &s) = default;
    ThreadPool &operator=(const ThreadPool &s) = default;
    ~ThreadPool();
    void threadFunc(int threadId);
public:
    static ThreadPool *getInstance();
    void setLog(LogLog log);
    // 开启线程池,指定线程里面的数量的多少,初始化这里给一次就可以了
    // 创建完最好等一会可以临时减少线程同时创建时取到相同任务的概率
    void start();
    void exit();

    // 启动之后不允许设置线程池状态
    bool getPoolState();

    ThreadPoolMode getThreadPoolMode() const;
    void setThreadPoolMode(ThreadPoolMode mode);

    void setThreadMaxThreshHold(int threshHold);
    int getThreadMaxThreshHold() const;

    void setTaskQueMaxThreshHold(int threshHold);
    int getTaskQueMaxThreshHold() const;

    int getCoreThreadSize() const;
    int getCurThreadSize() const;
    int getIdleThreadSize() const;
    int getTaskSize() const;

    bool taskIsFull() const;

    // 给线程池提交任务
    // 使用可变参模板变成，让submitTask接受任意任务函数和任意数量的参数
    // pool.submitTask(sum1,10,20)
    // 因为模板其实是在编译时并根据具体的类型对模板进行实例化，也就是说你想要调用的模板函数其实在编译时才生成了对应类型的函数。然而因为头文件和源文件分离，编译器无法得知你具体想要实例化的类型，所以才会出错。
    // 如果不想有返回可以pool->submitTask(sum, i, 2);或者 std::future<void> res =
    // pool->submitTask(sum, i, 2);没有get std::future<int> res =
    // pool->submitTask(sum, i, 2);会变成阻塞函数直到res.get() 有效
    template <typename Func, typename... Args>
    auto submitTask(Func &&func, Args &&...args)
    -> std::future<decltype(func(args...))> {
        // 打包任务，放入任务队列
        // 返回值类型重命名
        using RType = decltype(func(args...));
        auto task = std::make_shared<std::packaged_task<RType()>>(
                std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
        std::unique_lock<std::mutex> lock(_taskQueMtx);
        // 线程的通信   --判断是否队列中的任务到达了上限
        // 在 nonempty 条件变量进行等待,Pred条件不满足才会跳出wait
        // 用户提交任务，最长不能超过1s，否则判断任务失败，返回
        // wait(与时间无关) wait_for(持续等待的时间)  wait_until(等待的终点)
        // wait_for 返回值判断
        if (!_notFull.wait_for(lock, std::chrono::seconds(1), [&]() -> bool {
            return _taskQue.size() < _taskQueMaxThreshHold;
        })) {
            // 返回值为false，条件依旧没有满足，任务队列满的
            auto task = std::make_shared<std::packaged_task<RType()>>(
                    []() -> RType { return RType(); } // 返回一个空值的任务
            );
            (*task)(); // 相当于主线程来执行这个不成功的例子了
            return task->get_future(); // Task  Result
        }
        // taskQue_.push(std::move(sp));// bug
        // taskQue_.emplace(sp);
        //  using Task = std::function<void()>;
        _taskQue.emplace(
                [task]() { // 以值形式拷贝，相当于shared_ptr的拷贝了一份，疑问，这个函数体返回值不是void吗
                    // 去执行下面的任务
                    (*task)(); // 执行任务，以及设置任务的返回值
                });
        _taskSize++;
        // 通知消费者消费
        _nonEmpty.notify_all();

        // cached模式 需要根据任务数量和空闲线程数量，判断是否需要创建新的线程
        // 应用场景： 小而快的任务
        if (_poolMode == ThreadPoolMode::ThreadPoolModeCACHED &&
            _taskSize > _idleThreadSize && _curThreadSize < _threadMaxThreshHold) {
            // LogD("create new thread");
            //  创建新的线程对象
            auto ptr = std::make_unique<Thread>(
                    std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
            int threadId = ptr->getId();
            _threads.emplace(threadId, std::move(ptr));
            _threads[threadId]->start();
            // 修改线程个数相关的变量
            _curThreadSize++;
            _idleThreadSize++;
        }
        return task->get_future();
    }
private:
    std::unordered_map<int, std::unique_ptr<Thread>> _threads; // 线程列表
    int _coreThreadSize;            // 核心线程数量
    std::atomic_int _curThreadSize; // 记录当前线程池里面的线程总数量
    int _threadMaxThreshHold;       // 线程数量的上限阈值
    std::atomic_int _idleThreadSize; // 记录空闲线程的数量

    using Task = std::function<void()>;
    std::queue<Task>    _taskQue; // 这里不用基类的裸指针，并且这里保证要基类，因为我们要用到多态
    std::atomic_int _taskSize; // 任务的数量
    int _taskQueMaxThreshHold; // 任务最大的上限，超过上线会阻塞生产者，直到队列有新的线程执行任务

    std::mutex _taskQueMtx;            // 保证任务队列的原子性
    std::condition_variable _notFull;  // 表示任务队列不满
    std::condition_variable _nonEmpty; // 表示任务队列不空
    std::condition_variable _exitCond; // 等待线程资源全部回收

    ThreadPoolMode _poolMode;        // 线程模式
    std::atomic_bool _isPoolRunning; // 线程池是否在运行
};
#endif
